﻿using System;
using System.Collections.Generic;
using System.Linq;
using Creventive.Wpf.ShellFactory.Services;
using Creventive.Wpf.ShellFactory.Workspace.Primitives;

namespace Creventive.Wpf.ShellFactory.Workspace
{
    /// <summary>
    ///   Provides view models for views.
    /// </summary>
    public static class ApplicationViewModelFactory
    {
        #region Static Members

        private static Dictionary<Type, List<Type>> viewModelLookups = new Dictionary<Type, List<Type>>();

        /// <summary>
        ///   Registers the given model type for the given view type.
        /// </summary>
        /// <param name = "viewModelType">Type of the view model.</param>
        /// <param name = "viewModelTargetType">Type of the view.</param>
        public static void RegisterModel(Type viewModelType, Type viewModelTargetType)
        {
            if (viewModelTargetType == null)
                throw new ArgumentNullException("viewModelTargetType");
            if (viewModelType == null)
                throw new ArgumentNullException("viewModelType");

            if (viewModelType.IsAbstract)
                throw new ArgumentException("The view model " + viewModelType.FullName + " is abstract");
            bool isValidModelType;
            if (viewModelTargetType.IsInterface)
                isValidModelType = viewModelType.GetInterfaces().Contains(viewModelTargetType);
            else
                isValidModelType = viewModelTargetType.IsAssignableFrom(viewModelType);
            if (!isValidModelType)
                throw new ArgumentException("The view model " + viewModelType.FullName + " does not derive from " + viewModelTargetType.FullName);

            List<Type> modelList;
            if (!viewModelLookups.TryGetValue(viewModelTargetType, out modelList))
                viewModelLookups[viewModelTargetType] = modelList = new List<Type>();
            modelList.Add(viewModelType);
        }

        /// <summary>
        ///   Registers the given model type for the given view type.
        /// </summary>
        /// <typeparam name = "Tmodel">The type of the model.</typeparam>
        /// <typeparam name = "Tview">The type of the view.</typeparam>
        public static void RegisterModel<Tmodel, Tview>()
            where Tview : ApplicationView
            where Tmodel : ApplicationViewModel
        {
            RegisterModel(typeof(Tmodel), typeof(Tview));
        }

        /// <summary>
        ///   Creates the specified target view.
        /// </summary>
        /// <param name = "targetView">The target view.</param>
        /// <returns></returns>
        public static ApplicationViewModel Create(ApplicationView targetView)
        {
            if (targetView == null)
                throw new ArgumentNullException("targetView");

            Type desiredViewModelType = targetView.ViewModelType;
            if (desiredViewModelType == null)
                throw new InvalidOperationException("Target View has no defined ViewModelType");

            List<Type> modelList;
            var viewType = targetView.GetType();
            if (!viewModelLookups.TryGetValue(desiredViewModelType, out modelList))
                throw new InvalidOperationException("Cannot find a view model for a view of type " + viewType.FullName + ".");
            Type detectedType = modelList.Last();
            bool isLegalType;
            if (desiredViewModelType.IsInterface)
                isLegalType = detectedType.GetInterfaces().Contains(desiredViewModelType);
            else
                isLegalType = desiredViewModelType.IsAssignableFrom(detectedType);
            if (!isLegalType)
                throw new InvalidOperationException("The type " + detectedType.FullName + " does not derive from " + desiredViewModelType.FullName);
            return (ApplicationViewModel)targetView.Services.Build(detectedType);
        }

        #endregion
    }
}